using System;
using System.Collections.Generic;
using System.Text;

namespace JMKeyLib {
  using System.Data;
  using System.Xml;
  using System.Security.Cryptography;

  /// <summary>
  /// Selects the set to be used in conjunction with the # mask.
  /// </summary>
  public enum AlphaNumericFilter : int {
    /// <summary>
    /// Only allows Uppercase letters to be used in the # mask.
    /// </summary>
    UppercaseOnly = 0,
    /// <summary>
    /// Only allows Lowercase letters to be used in the # mask.
    /// </summary>
    LowercaseOnly = 1,
    /// <summary>
    /// Allows Uppercase and Lowercase letters to be used in the # mask.
    /// </summary>
    AnyCase = 2
  }

  /// <summary>
  /// Selects the set to be used in conjunction with the @ mask.
  /// </summary>
  public enum AlphabetOnlyFilter : int {
    /// <summary>
    /// Only allows Uppercase letters to be used in the @ mask.
    /// </summary>
    UppercaseOnly = 0,
    /// <summary>
    /// Only allows Lowercase letters to be used in the @ mask.
    /// </summary>
    LowercaseOnly = 1,
    /// <summary>
    /// Allows Uppercase and Lowercase letters to be used in the @ mask.
    /// </summary>
    AnyCase = 2
  }

  /// <summary>
  /// Creates Custom keys based on user defined variables.
  /// </summary>
  public class KeyGenerator : IDisposable {
    #region "Class Assigned Variables"
    private char[] reservedChars;

    private string alphaNumUpper;
    private string alphaNumLower;
    private string alphaNumBoth;

    private string alphaOnlyUpper;
    private string alphaOnlyLower;
    private string alphaOnlyBoth;

    private string numOnly;

    private int AlphaNumFilter;
    private int AlphaFilter;

    private DataTable KeyData;

    private byte[] randomBytes;
    RNGCryptoServiceProvider rng;
    char returnChar;

    private bool karat = false;
    private bool aster = false;
    private bool tilde = false;

    private int karatIndex;
    private int asterIndex;
    private int tildeIndex;
    #endregion

    #region "Program Assigned Variables / Properties";
    private string KeyMask = "";
    /// <summary>
    /// Gets / Sets the Key Template Mask for the KeyGenerator Class.
    /// </summary>
    public string KeyTemplateMask {
      get { return KeyMask; }
      set { KeyMask = value; }
    }
    private string FirstOccurrence = "";
    /// <summary>
    /// Gets / Sets the 1st Custom Field Set string.
    /// </summary>
    public string CustomFieldSet1 {
      get { return FirstOccurrence; }
      set { FirstOccurrence = value; }
    }
    private string SecondOccurrence = "";
    /// <summary>
    /// Gets / Sets the 2nd Custom Field Set string.
    /// </summary>
    public string CustomFieldSet2 {
      get { return SecondOccurrence; }
      set { SecondOccurrence = value; }
    }
    private string ThirdOccurrence = "";
    /// <summary>
    /// Gets / Sets the 3rd Custom Field Set string.
    /// </summary>
    public string CustomFieldSet3 {
      get { return ThirdOccurrence; }
      set { ThirdOccurrence = value; }
    }
    private string FourthOccurrence = "";
    /// <summary>
    /// Gets / Sets the 4th Custom Field Set string.
    /// </summary>
    public string CustomFieldSet4 {
      get { return FourthOccurrence; }
      set { FourthOccurrence = value; }
    }
    private string FifthOccurrence = "";
    /// <summary>
    /// Gets / Sets the 1st Custom Field Set string.
    /// </summary>
    public string CustomFieldSet5 {
      get { return FifthOccurrence; }
      set { FifthOccurrence = value; }
    }
    private string SixthOccurrence = "";
    /// <summary>
    /// Gets / Sets the 1st Custom Field Set string.
    /// </summary>
    public string CustomFieldSet6 {
      get { return SixthOccurrence; }
      set { SixthOccurrence = value; }
    }
    #endregion

    #region "KeyGenerator Class Constructors"
    public KeyGenerator(AlphaNumericFilter AlphaNumeric, AlphabetOnlyFilter AlphabetOnly) {
      InitializeComponent();
      this.AlphaNumFilter = (int)AlphaNumeric;
      this.AlphaFilter = (int)AlphabetOnly;
    }
    public KeyGenerator(string KeyFormatMask, AlphaNumericFilter AlphaNumeric, AlphabetOnlyFilter AlphabetOnly) {
      InitializeComponent();
      this.KeyMask = KeyFormatMask;
      this.AlphaNumFilter = (int)AlphaNumeric;
      this.AlphaFilter = (int)AlphabetOnly;
    }
    public KeyGenerator(string KeyFormatMask, string[] CustomKeyFields, AlphaNumericFilter AlphaNumeric, AlphabetOnlyFilter AlphabetOnly) {
      InitializeComponent();
      this.KeyMask = KeyFormatMask;
      this.AlphaNumFilter = (int)AlphaNumeric;
      this.AlphaFilter = (int)AlphabetOnly;

      // This Constructor is not yet used. The Implementation will be figured out in 0.8.* release
      throw new NotImplementedException("This Constructor is reserved for future use.");
    }
    #endregion

    #region "Construction Routines"
    /// <summary>
    /// Intializes all Components needed for the KeyGenerator Class.
    /// </summary>
    private void InitializeComponent() {
      try {
        createKeyData();

        reservedChars = ("`~!@#$%^&*()_+=\\][{}|;'\":/?.>,<".ToCharArray());

        alphaNumUpper = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
        alphaNumLower = "abcdefghjkmnpqrstuvwxyz23546789";
        alphaNumBoth = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";

        alphaOnlyUpper = "ABCDEFGHJKLMNPQRSTUVWXYZ";
        alphaOnlyLower = "abcdefghjkmnpqrstuvwxyz";
        alphaOnlyBoth = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";

        numOnly = "23456789";
      }
      catch (Exception e) {
        throw new Exception("The Key Generator failed to intialize.\r\n" + e.Message);
      }
    }
    /// <summary>
    /// Initialized the DataTable to handle generated key inputs.
    /// </summary>
    private void createKeyData() {
      KeyData = new DataTable("KeyData");

      DataColumn clmnKey = new DataColumn("UniqueKey", Type.GetType("System.String"));
      clmnKey.Unique = true;

      KeyData.Columns.Add(clmnKey);
    }
    #endregion

    #region "Private Key Processing Methods"
    /// <summary>
    /// Generates 1 Key based on criteria passed to the KeyGenerator Class.
    /// </summary>
    private void CreateKey() {
      char[] formatMask = KeyMask.ToCharArray();
      int customOcc = 0;

      for (int j = 0; j < formatMask.Length; j++) {
        if (formatMask[j] == '?') { formatMask[j] = getCustomSet(++customOcc); }
        else if (formatMask[j] == '^') {
          if (!karat) { formatMask[j] = getAlpha(); karat = true; karatIndex = j; }
          else { formatMask[j] = formatMask[karatIndex]; }
        }
        else if (formatMask[j] == '*') {
          if (!aster) { formatMask[j] = getNumeric(); aster = true; asterIndex = j; }
          else { formatMask[j] = formatMask[asterIndex]; }
        }
        else if (formatMask[j] == '~') {
          if (!tilde) { formatMask[j] = getAlphaNumeric(); tilde = true; tildeIndex = j; }
          else { formatMask[j] = formatMask[tildeIndex]; }
        }
        else if (formatMask[j] == '#') { formatMask[j] = getAlphaNumeric(); }
        else if (formatMask[j] == '@') { formatMask[j] = getAlpha(); }
        else if (formatMask[j] == '%') { formatMask[j] = getNumeric(); }
      }
      DataRow dr = KeyData.NewRow();
      dr[0] = new string(formatMask);

      try {
        KeyData.Rows.Add(dr);
      }
      catch { /* Do Nothing: The Value is not Unique and needs to be omitted! */ }
      finally {
        formatMask = null;
        dr = null;
      }
    }
    /// <summary>
    /// Resets the the operators for a new key.
    /// </summary>
    private void ResetIndexes() {
      this.karat = false;
      this.aster = false;
      this.tilde = false;

      this.randomBytes = null;
      this.rng = null;
      //this.returnChar = null;
    }

    private char getCustomSet(int Occurrence) {
      randomBytes = new byte[1];
      rng = new RNGCryptoServiceProvider();
      rng.GetBytes(randomBytes);

      string customSet = "";

      if (Occurrence == 1) { customSet = FirstOccurrence; }
      else if (Occurrence == 2) { customSet = SecondOccurrence; }
      else if (Occurrence == 3) { customSet = ThirdOccurrence; }
      else if (Occurrence == 4) { customSet = FourthOccurrence; }
      else if (Occurrence == 5) { customSet = FifthOccurrence; }
      else if (Occurrence == 6) { customSet = SixthOccurrence; }

      if (Occurrence > 6) { throw new Exception("A maximum of 6 \"?\" may occur wihin in a Key Template Mask."); }
      if (customSet.Length == 0) { throw new Exception("A custom feild set must be filled out for each occurrence of a \"?\"."); }

      this.returnChar = customSet[(int)randomBytes[0] % customSet.Length];
      customSet = null;

      return this.returnChar;
    }

    private char getAlpha() {
      randomBytes = new byte[1];
      rng = new RNGCryptoServiceProvider();
      rng.GetBytes(randomBytes);

      if (AlphaFilter == 0) { this.returnChar = alphaOnlyUpper[(int)randomBytes[0] % alphaOnlyUpper.Length]; }
      else if (AlphaFilter == 1) { this.returnChar = alphaOnlyLower[(int)randomBytes[0] % alphaOnlyLower.Length]; }
      else { this.returnChar = alphaOnlyBoth[(int)randomBytes[0] % alphaOnlyBoth.Length]; }

      return this.returnChar;
    }

    private char getAlphaNumeric() {
      randomBytes = new byte[1];
      rng = new RNGCryptoServiceProvider();
      rng.GetBytes(randomBytes);

      if (AlphaNumFilter == 0) { this.returnChar = alphaNumUpper[(int)randomBytes[0] % alphaNumUpper.Length]; }
      else if (AlphaNumFilter == 1) { this.returnChar = alphaNumLower[(int)randomBytes[0] % alphaNumLower.Length]; }
      else { this.returnChar = alphaNumBoth[(int)randomBytes[0] % alphaNumBoth.Length]; }

      return this.returnChar;
    }

    private char getNumeric() {
      randomBytes = new byte[1];
      rng = new RNGCryptoServiceProvider();
      rng.GetBytes(randomBytes);

      return numOnly[(int)randomBytes[0] % numOnly.Length];
    }
    #endregion

    #region "Public Key Processing Methods"
    public DataTable GetKeySets() {
      return this.KeyData;
    }
    /// <summary>
    /// Generates 1 Key and Resets Indexers.
    /// </summary>
    public void GenerateKey() {
      this.CreateKey();
      this.ResetIndexes();
    }
    #endregion

    #region "Garbage Collection Routines"
    /// <summary>
    /// Deconstrucor Call for KeyGenerator;
    /// </summary>
    ~KeyGenerator() {
      Dispose();
    }
    /// <summary>
    /// Releases all resources to the Garbage Collector.
    /// </summary>
    public void Dispose() {
      this.reservedChars = null;

      this.alphaNumUpper = null;
      this.alphaNumLower = null;
      this.alphaNumBoth = null;

      this.alphaOnlyUpper = null;
      this.alphaOnlyLower = null;
      this.alphaOnlyBoth = null;

      this.KeyData = null;

      this.randomBytes = null;
      this.rng = null;

      this.KeyTemplateMask = null;
      this.FirstOccurrence = null;
      this.SecondOccurrence = null;
      this.ThirdOccurrence = null;
      this.FourthOccurrence = null;
      this.FifthOccurrence = null;
      this.SixthOccurrence = null;
    }
    #endregion
  }
}